/*
    path.c.tst - Unit tests for the mprPath module

    Copyright (c) All Rights Reserved. See details at the end of the file.
 */

/********************************** Includes **********************************/

#include    "testme.h"
#include    "mpr.h"

/*********************************** Locals **********************************/

#define DIR1        ".mpr-testTempDir"
#define DIR2        ".mpr-testSubDir"
#define DIRMODE     0755

typedef struct TestPath {
    char    *dir1;
    char    *dir2;
} TestPath;

/************************************ Code ************************************/

static void copyPath()
{
    MprFile     *file;
    char        *from, *to;

    from = sfmt("copyTest-%s.tmp", mprGetCurrentThreadName());
    ttrue(from != 0);
    file = mprOpenFile(from, O_CREAT | O_TRUNC | O_WRONLY, 0664);
    ttrue(file != 0);
    mprWriteFileString(file, "Hello World");
    mprCloseFile(file);

    to = sfmt("newTest-%s.tmp", mprGetCurrentThreadName());
    ttrue(mprPathExists(from, F_OK));
    mprCopyPath(from, to, 0664);
    ttrue(mprPathExists(to, F_OK));
    mprDeletePath(from);
    mprDeletePath(to);
}


static void absPath()
{
    char        *path;

#if MANUAL_TESTING
    path = mprNormalizePath("/");
    path = mprNormalizePath("C:/");
    path = mprNormalizePath("C:/abc");
    path = mprNormalizePath("");
    path = mprNormalizePath("c:abc");
    path = mprNormalizePath("abc");

    path = mprGetAbsPath("/");
    path = mprGetAbsPath("C:/");
    path = mprGetAbsPath("C:/abc");
    path = mprGetAbsPath("");
    path = mprGetAbsPath("c:abc");
    path = mprGetAbsPath("abc");
#endif

    path = mprGetAbsPath("");
    ttrue(mprIsPathAbs(path));
    
    path = mprGetAbsPath("/");
    ttrue(mprIsPathAbs(path));
    
    path = mprGetAbsPath("../../../../../../../../../../..");
    ttrue(mprIsPathAbs(path));
    ttrue(mprIsPathAbs(mprGetAbsPath("Makefile")));

    /*
        Manually check incase mprIsAbs gets it wrong
     */
    path = mprGetAbsPath("Makefile");
    ttrue(path && *path);
    ttrue(mprIsPathAbs(path));
#if ME_WIN_LIKE
    ttrue(isalpha(path[0]));
    ttrue(path[1] == ':' && path[2] == '\\');
#elif ME_UNIX_LIKE
    ttrue(path[0] == '/' && path[1] != '/');
#endif
    ttrue(strcmp(mprGetPathBase(path), "Makefile") == 0);
}


static bool check(MprList *list, cchar *expect)
{
    return smatch(mprListToString(list, ","), expect);
}


static void globPath()
{
    MprList     *list;
    cchar       *dir;

    dir = "data";

    //  Null args
    list = mprGlobPathFiles(NULL, NULL, 0);
    ttrue(check(list, ""));

    //  Empty args
    list = mprGlobPathFiles(NULL, "", 0);
    ttrue(check(list, ""));

    list = mprGlobPathFiles("", "", 0);
    ttrue(check(list, ""));

    //  Empty should match nothing
    list = mprGlobPathFiles(".", "", 0);
    ttrue(check(list, ""));
    
    //  Single wildcard
    list = mprGlobPathFiles(".", "*", 0);
    tfalse(check(list, ""));
    
    //  Test known directory contents
    list = mprGlobPathFiles(dir, "*", 0);
    ttrue(mprGetListLength(list) == 6);

    //  Test with path in pattern
    list = mprGlobPathFiles(".", "data/*", 0);
    ttrue(mprGetListLength(list) == 6);

    //  Pass absolute path -- absolute results
    list = mprGlobPathFiles(mprGetCurrentPath(), "**", 0);
    tfalse(check(list, ""));
    ttrue(mprIsPathAbs(mprGetFirstItem(list)));

    //  Relative results with an absolute path
    list = mprGlobPathFiles(mprGetCurrentPath(), "**", MPR_PATH_RELATIVE);
    tfalse(mprIsPathAbs(mprGetFirstItem(list)));

    //  Relative with an absolute pattern
    list = mprGlobPathFiles(".", mprGetCurrentPath(), MPR_PATH_RELATIVE);
    ttrue(mprGetListLength(list) == 1);
    ttrue(smatch(mprGetFirstItem(list), "."));

    //  Relative of root
#if ME_UNIX_LIKE
    list = mprGlobPathFiles("/", "*", MPR_PATH_RELATIVE);
    tfalse(check(list, ""));
#endif

    //  Relative with a directory
    list = mprGlobPathFiles(dir, "mid/*.dat", MPR_PATH_RELATIVE);
    ttrue(mprGetListLength(list) == 1);
    ttrue(smatch(mprGetFirstItem(list), mprGetNativePath("mid/middle.dat")));

    //  With hidden files (one more file -- 7)
    list = mprGlobPathFiles(dir, "*", MPR_PATH_INC_HIDDEN);
    ttrue(mprGetListLength(list) == 7);

    //  Double wild - recursive
    list = mprGlobPathFiles(dir, "**", 0);
    ttrue(mprGetListLength(list) == 13);

    //  Double wild suffix
    list = mprGlobPathFiles(dir, "m**", 0);
    ttrue(mprGetListLength(list) == 8);

    //  Double wild suffix
    list = mprGlobPathFiles(dir, "m**", 0);
    ttrue(mprGetListLength(list) == 8);

    //  Double wild prefix
    list = mprGlobPathFiles(dir, "**/*.dat", 0);
    ttrue(mprGetListLength(list) == 10);

    //  Embedded double wild
    list = mprGlobPathFiles(dir, "mid/**/leaf*", 0);
    ttrue(mprGetListLength(list) == 4);

    //  Embedded double wild
    list = mprGlobPathFiles(dir, "mid/**/*.dat", 0);
    ttrue(mprGetListLength(list) == 5);

    //  Trailing double wild
    list = mprGlobPathFiles(dir, "mid/sub*/**", 0);
    ttrue(mprGetListLength(list) == 6);

    //  Descend
    list = mprGlobPathFiles(dir, "mid", MPR_PATH_DESCEND);
    ttrue(mprGetListLength(list) == 12);

    //  No directories
    list = mprGlobPathFiles(dir, "mid", MPR_PATH_NO_DIRS);
    ttrue(mprGetListLength(list) == 0);
    list = mprGlobPathFiles(dir, "mid/**", MPR_PATH_NO_DIRS);
    ttrue(mprGetListLength(list) == 5);

    //  Pattern with absolute path
    list = mprGlobPathFiles(".", mprGetCurrentPath(), 0);
    ttrue(mprGetListLength(list) == 1);

    //  Pattern with absolute path
    list = mprGlobPathFiles(".", mprJoinPath(mprGetCurrentPath(), "data"), 0);
    ttrue(mprGetListLength(list) == 1);

    //  Relative files
    list = mprGlobPathFiles(dir, "**", MPR_PATH_RELATIVE);
    ttrue(mprGetListLength(list) == 13);
    ttrue(!mprIsPathAbs(mprGetFirstItem(list)));

    //  Absolute pattern with relative results
    list = mprGlobPathFiles(".", sjoin(mprGetCurrentPath(), "/data/**", 0), MPR_PATH_RELATIVE);
    ttrue(!mprIsPathAbs(mprGetFirstItem(list)));
    ttrue(mprGetListLength(list) == 13);

    //  Absolute pattern without relative results
    list = mprGlobPathFiles(".", sjoin(mprGetCurrentPath(), "/data/**", 0), 0);
    ttrue(mprIsPathAbs(mprGetFirstItem(list)));
    ttrue(mprGetListLength(list) == 13);

    // Stress and regression
    list = mprGlobPathFiles(".", sjoin(mprGetCurrentPath(), "/data/a.dat", 0), 0);
    ttrue(mprIsPathAbs(mprGetFirstItem(list)));
    ttrue(mprGetListLength(list) == 1);

    list = mprGlobPathFiles(".", sjoin(mprGetCurrentPath(), "/data/**/leaf1.dat", 0), 0);
    ttrue(mprIsPathAbs(mprGetFirstItem(list)));
    ttrue(mprGetListLength(list) == 1);

    list = mprGlobPathFiles(dir, "**/*.dat", 0);
    ttrue(mprGetListLength(list) == 10);

    list = mprGlobPathFiles(dir, "mid/middle.dat", MPR_PATH_RELATIVE);
    ttrue(smatch(mprGetFirstItem(list), mprGetNativePath("mid/middle.dat")));

    /*
        print("%s", mprListToString(list, "\n"));
     */
}


static void joinPath()
{
#if ME_WIN_LIKE
    ttrue(smatch(mprJoinPath("\\tmp", "Makefile"), "\\tmp\\Makefile"));
    ttrue(smatch(mprJoinPath("\\tmp", "\\Makefile"), "\\Makefile"));
    ttrue(smatch(mprJoinPath("\\tmp", NULL), "\\tmp"));
    ttrue(smatch(mprJoinPath("\\tmp", "."), "\\tmp"));
    ttrue(smatch(mprJoinPath("\\\\\\tmp\\\\\\", "Makefile\\\\\\"), "\\tmp\\Makefile"));
#else
    ttrue(smatch(mprJoinPath("/tmp", "Makefile"), "/tmp/Makefile"));
    ttrue(smatch(mprJoinPath("/tmp", "/Makefile"), "/Makefile"));
    ttrue(smatch(mprJoinPath("/tmp", NULL), "/tmp"));
    ttrue(smatch(mprJoinPath("/tmp", "."), "/tmp"));
    ttrue(smatch(mprJoinPath("///tmp///", "Makefile///"), "/tmp/Makefile"));
#endif
    ttrue(smatch(mprJoinPath("", "Makefile"), "Makefile"));

    ttrue(smatch(mprJoinPathExt("/abc", ".exe"), "/abc.exe"));
    ttrue(smatch(mprJoinPathExt("/abc.bat", ".exe"), "/abc.bat"));
    ttrue(smatch(mprJoinPathExt("/abc", ""), "/abc"));
    ttrue(smatch(mprJoinPathExt("ejs.web/file", ".mod"), "ejs.web/file.mod"));
}


static void makeDir()
{
    int         rc;
    
    rmdir(DIR1);
    rc = mprMakeDir(DIR1, DIRMODE, -1, -1, 1);
    ttrue(rc == 0);
    ttrue(access(DIR1, X_OK) == 0);

    rmdir(DIR1);
    ttrue(access(DIR1, X_OK) < 0);

    /*
        Dir should not exist before test
     */
    ttrue(access(DIR1, X_OK) < 0);

    /*
        Make directory path
     */
    rmdir(DIR2);
    rc = mprMakeDir(DIR2, DIRMODE, -1, -1, 1);
    ttrue(rc == 0);
    ttrue(access(DIR2, X_OK) == 0);

    rc = mprDeletePath(DIR2);
    ttrue(rc == 0);
    
    ttrue(access(DIR1, X_OK) < 0);
    ttrue(access(DIR2, X_OK) < 0);
}


static void matchPath()
{
    //  Null args
    tfalse(mprMatchPath(NULL, NULL));
    tfalse(mprMatchPath("abc", NULL));
    tfalse(mprMatchPath(NULL, "abc"));

    //  Empty args
    ttrue(mprMatchPath("", ""));
    tfalse(mprMatchPath("abc", ""));
    tfalse(mprMatchPath("", "abc"));

    //  Substrings
    ttrue(mprMatchPath("abc", "abc"));
    tfalse(mprMatchPath("abc", "abcd"));
    tfalse(mprMatchPath("abc", "ab"));

    //  Subpaths
    ttrue(mprMatchPath("/a/b", "/a/b"));
    tfalse(mprMatchPath("/a/b", "/a/b/c"));
    tfalse(mprMatchPath("/a/b", "/a/"));

    //  Trailing separators
    ttrue(mprMatchPath("/a/b/c", "/a/b/c/"));
    ttrue(mprMatchPath("/a/b/c/", "/a/b/c"));

    //  Wild 
    ttrue(mprMatchPath("abc", "*"));
    ttrue(mprMatchPath("abc", "a*"));
    ttrue(mprMatchPath("abc", "*c"));
    ttrue(mprMatchPath("abc", "a*c"));
    tfalse(mprMatchPath("abc", "a*d"));

    //  Single char
    ttrue(mprMatchPath("abc", "???"));
    tfalse(mprMatchPath("abc", "??"));
    tfalse(mprMatchPath("abc", "?"));
    ttrue(mprMatchPath("abc", "?*"));
    ttrue(mprMatchPath("abc", "*?"));

    //  Double wild
    ttrue(mprMatchPath("a/b/c", "**"));
    ttrue(mprMatchPath("a/b/c", "**c"));
    ttrue(mprMatchPath("a/b/c", "**/c"));
    ttrue(mprMatchPath("a/b/c", "**/*c"));
    ttrue(mprMatchPath("a/b/c", "a/**c"));
    ttrue(mprMatchPath("a/b/c", "a/**/*c"));

    tfalse(mprMatchPath("a/b/c", "a/**/d"));
    tfalse(mprMatchPath("a/b/c", "b/**"));
    tfalse(mprMatchPath("a/b/c", "**/x/c"));

    //  Non-canonical separators
    ttrue(mprMatchPath("a////b", "a/b"));
    ttrue(mprMatchPath("a/b", "a/////b"));

    //  Trailing separators
    ttrue(mprMatchPath("a/b/", "a/b"));
    ttrue(mprMatchPath("a/b", "a/b/"));

    //  Quad wild
    ttrue(mprMatchPath("a/b/c", "****c"));
    ttrue(mprMatchPath("a/b/c", "**/**c"));

    //  Pattern in directory
    ttrue(mprMatchPath("a.c", "**/a.c"));
    ttrue(mprMatchPath("a.c/a.c", "**/a.c"));
    ttrue(mprMatchPath("a.c/a.c/a.c", "**/a.c"));
    ttrue(mprMatchPath("a.c/a.c/a.c", "**/a.c/a.c"));
    ttrue(mprMatchPath("a.c", "**/a.c/a.c") == 0);
    ttrue(mprMatchPath("a.c/a.c", "**/a.c/a.c"));

    //  Stress
    ttrue(mprMatchPath("/a/b/c/d/e/f/g", "/a**"));
    ttrue(mprMatchPath("/a/b/c/d/e/f/g", "/a**/c/**/g"));
    tfalse(mprMatchPath("/a/b/c/d/e/f/g", "/a**/c/**/h"));
    tfalse(mprMatchPath("/a/b/c/d/e/f/g", "/a**/k/**/g"));
}


static void normalize()
{
    char        *path;

    ttrue(strcmp(mprNormalizePath(""), "") == 0);
    ttrue(strcmp(mprNormalizePath("/"), "/") == 0);
    ttrue(strcmp(mprNormalizePath("."), ".") == 0);
    ttrue(strcmp(mprNormalizePath("abc/"), "abc") == 0);
    ttrue(strcmp(mprNormalizePath("./"), ".") == 0);
    ttrue(strcmp(mprNormalizePath("../"), "..") == 0);
    ttrue(strcmp(mprNormalizePath("/.."), "/") == 0);
    ttrue(strcmp(mprNormalizePath("./.."), "..") == 0);
    ttrue(strcmp(mprNormalizePath("//.."), "/") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/.."), "/") == 0);
    ttrue(strcmp(mprNormalizePath("abc/.."), ".") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/../def"), "/def") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/../def/../xyz"), "/xyz") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/def/../.."), "/") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/def/../../xyz"), "/xyz") == 0);
    ttrue(strcmp(mprNormalizePath("/abc/def/.././../xyz"), "/xyz") == 0);
    ttrue(strcmp(mprNormalizePath("//a//b//./././c/d/e/f/../../g"), "/a/b/c/d/g") == 0);
    ttrue(strcmp(mprNormalizePath("../../modules/ejs.mod"), "../../modules/ejs.mod") == 0);

    path = mprNormalizePath("//a//b//./././c/d/e/f/../../g");
    ttrue(strcmp(path, "/a/b/c/d/g") == 0);

#if VXWORKS || ME_WIN_LIKE
    path = mprNormalizePath("\\\\a\\\\b\\\\.\\.\\.\\c\\d\\e\\f\\..\\..\\g");
    ttrue(strcmp(path, "\\a\\b\\c\\d\\g") == 0);
    ttrue(strcmp(mprNormalizePath("host:"), "host:.") == 0);
    ttrue(strcmp(mprNormalizePath("host:/"), "host:/") == 0);
    ttrue(strcmp(mprNormalizePath("host:////"), "host:/") == 0);
    ttrue(strcmp(mprNormalizePath("host:abc"), "host:abc") == 0);
    ttrue(strcmp(mprNormalizePath("c:abc"), "c:abc") == 0);
#endif
}


static void relPath()
{
    char    *path, *absPath;
    
    path = mprGetRelPath("Makefile", 0);
    ttrue(strcmp(path, "Makefile") == 0);
    
    path = mprNormalizePath("../a.b");
    ttrue(strcmp(path, "../a.b") == 0);

    path = mprGetRelPath("/", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);
    
    path = mprGetRelPath("//", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);
    
    path = mprGetRelPath("/tmp", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);

    path = mprGetRelPath("/Unknown/someone/junk", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);
           
    path = mprGetRelPath("/Users/mob/junk", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);
    
    path = mprGetRelPath("/Users/mob/././../mob/junk", 0);
    ttrue(mprIsPathRel(path));
    ttrue(strncmp(path, "../", 3) == 0);
    
    path = mprGetRelPath(".", 0);
    ttrue(strcmp(path, ".") == 0);

    path = mprGetRelPath("..", 0);
    ttrue(strcmp(path, "..") == 0);

    path = mprGetRelPath("/Users/mob/github/admin", 0);
    ttrue(sstarts(path, ".."));

    path = mprGetRelPath("/Users/mob/git", 0);
    path = mprGetRelPath("/Users/mob/git/mpr/test", 0);
    /* Can't really test the result of this */

    absPath = mprGetAbsPath("Makefile");
    ttrue(mprIsPathAbs(absPath));
    path = mprGetRelPath(absPath, 0);
    ttrue(!mprIsPathAbs(path));
    ttrue(strcmp(path, "Makefile") == 0);

#if FUTURE
    //  TODO - problem in that we don't know the cwd when testMpr runs
    //  Test relative to an origin

    out = mprGetAbsPath("../../out");
    cwd = mprGetCurrentPath();
    ttrue(smatch(mprGetRelPath(cwd, out), "../src/test"));
#endif
}


static void resolvePath()
{
#if FUTURE
    char    *path, *absPath, *out;
    
    out = mprGetAbsPath("../out");
    ttrue(smatch(mprResolvePath(out, mprGetCurrentPath()), "../src/test"));
#endif
}


static void same()
{
    /* Assumes running in test/utest/api */
    ttrue(mprSamePath("testFile", "./testFile"));
#if AVOID
    /*
        This forces the tests to be run in a specific directory
     */
    ttrue(mprSamePath("testFile", "../test/api/testFile"));
    ttrue(mprSamePath(mprGetAbsPath("testFile"), "../test/api/testFile"));
    ttrue(mprSamePath("testFile", mprGetAbsPath("../test/api/testFile")));
#endif
}


static void temp()
{
    char    *path;

    path = mprGetTempPath(NULL);
    ttrue(path && *path);
    ttrue(mprIsPathAbs(path));
    ttrue(mprPathExists(path, F_OK));
    mprDeletePath(path);
}


static void transform()
{
    char    *path;

    path = mprTransformPath("/", MPR_PATH_ABS);
    ttrue(mprIsPathAbs(path));

    path = mprTransformPath("/", MPR_PATH_REL);
    ttrue(mprIsPathRel(path));
    ttrue(path[0] == '.');

    path = mprTransformPath("/", 0);
    ttrue(smatch(path, "/"));

    path = mprTransformPath("/", MPR_PATH_ABS);
    ttrue(mprIsPathAbs(path));

#if ME_WIN_LIKE || CYGWIN
    /* Test MapSeparators */
    path = sclone("\\a\\b\\c\\d");
    mprMapSeparators(path, '/');
    ttrue(*path == '/');
    ttrue(strchr(path, '\\') == 0);

    /* Test PortablePath */
    path = sclone("\\a\\b\\c\\d");
    path = mprGetPortablePath(path);
    ttrue(*path == '/');
    ttrue(strchr(path, '\\') == 0);
    ttrue(mprIsPathAbs(path));
#endif

#if CYGWIN
    path = mprGetAbsPath("c:/a/b");
    ttrue(smatch(path, "/cygdrive/c/a/b"));
    path = mprGetAbsPath("/a/b");
    ttrue(smatch(path, "/a/b"));
    path = mprGetAbsPath("c:/cygwin/a/b");
    ttrue(smatch(path, "/a/b"));
    path = mprGetWinPath("c:/a/b");
    ttrue(smatch(path, "C:\\a\\b"));
    path = mprGetWinPath("/cygdrive/c/a/b");
    ttrue(smatch(path, "C:\\a\\b"));
#endif
}

    
#if KEEP
/*
    Make a unique filename for a given thread
 */
static char *makePath(cchar *name)
{
    return sfmt("%s-%d-%s", name, getpid(), mprGetCurrentThreadName());
}
#endif

int main(int argc, char **argv)
{
    mprCreate(argc, argv, 0);
    rmdir(DIR1);
    rmdir(DIR2);

    copyPath();
    absPath();
    globPath();
    joinPath();
    matchPath();
    makeDir();
    normalize();
    relPath();
    resolvePath();
    same();
    transform();
    temp();

    rmdir(DIR1);
    rmdir(DIR2);
    return 0;
}


/*
    @copy   default

    Copyright (c) Embedthis Software. All Rights Reserved.

    This software is distributed under commercial and open source licenses.
    You may use the Embedthis Open Source license or you may acquire a 
    commercial license from Embedthis Software. You agree to be fully bound
    by the terms of either license. Consult the LICENSE.md distributed with
    this software for full details and other copyrights.

    Local variables:
    tab-width: 4
    c-basic-offset: 4
    End:
    vim: sw=4 ts=4 expandtab

    @end
 */
